package controler;

import model.Cistern;
import model.Constants;
import model.Criteria;
import model.Environment;
import model.EnvironmentRegulator;
import model.Filter;
import model.LevelSensor;
import model.Pounding;
import model.SensedValue;
import model.Sensor;
import model.Stages;
import model.SystemMonitor;
import model.Valve;
import view.MainWindow;

public class PLC {

    // View
    private final MainWindow window;

    // Cisterns
    private final Cistern grapesContainer = new Cistern(Constants.GRAPES_CISTERN_CAPACITY, 1);
    private final Cistern alcoholicFermentationCistern = new Cistern(Constants.ALCOHOLIC_FERMENTATION_CISTERN_CAPACITY, 2);
    private final Cistern malolacticFermentationCistern = new Cistern(Constants.MALOLACTIC_FERMENTATION_CISTERN_CAPACITY, 3);
    private final Cistern storageCistern = new Cistern(Constants.STORAGE_CISTERN_CAPACITY, 4);

    // Filters
    private final Pounding pounding = new Pounding(1);
    private final Filter aFToMFFilter = new Filter(Constants.ALCOHOLIC_TO_MALOLACTIC_FILTER, 1);
    private final Filter mFToStorageFilter = new Filter(Constants.MALOLACTIC_TO_STORAGE_FILTER, 2);

    // Valves
    private final Valve grapesToAFValve = new Valve(grapesContainer, alcoholicFermentationCistern, pounding, 1);
    private final Valve aFToMFValve = new Valve(alcoholicFermentationCistern, malolacticFermentationCistern, aFToMFFilter, 2);
    private final Valve mFToStorageValve = new Valve(malolacticFermentationCistern, storageCistern, mFToStorageFilter, 3);

    // Environment
    private final Environment temperatureEnvironment = new Environment(new SensedValue(Constants.START_TEMPERATURE), Constants.MIN_TEMPERATURE, Constants.MAX_TEMPERATURE, Constants.SPEED_TEMPERATURE,
            1);
    private final Environment luminosityEnvironment = new Environment(new SensedValue(Constants.START_LUMINOSITY), Constants.MIN_LUMINOSITY, Constants.MAX_LUMINOSITY, Constants.SPEED_LUMINOSITY, 2);
    private final Environment humidityEnvironment = new Environment(new SensedValue(Constants.START_HUMIDITY), Constants.MIN_HUMIDITY, Constants.MAX_HUMIDITY, Constants.SPEED_HUMIDITY, 3);

    // Environment Regulator
    private final EnvironmentRegulator temperatureRegulator = new EnvironmentRegulator(temperatureEnvironment, 1);
    private final EnvironmentRegulator luminosityRegulator = new EnvironmentRegulator(luminosityEnvironment, 2);
    private final EnvironmentRegulator humidityRegulator = new EnvironmentRegulator(humidityEnvironment, 3);

    // Sensors
    private final Sensor temperatureSensor = new Sensor(temperatureEnvironment, Constants.SECONDS, 1);
    private final Sensor luminositySensor = new Sensor(luminosityEnvironment, Constants.SECONDS, 2);
    private final Sensor humiditySensor = new Sensor(humidityEnvironment, Constants.SECONDS, 3);

    private final LevelSensor lowSensorGrapesContainer = new LevelSensor(Constants.LOW_LEVEL_GRAPES, Criteria.LOWER, new Sensor(grapesContainer, Constants.SECONDS, 0), 1);
    private final LevelSensor lowSensorAlcoholicFermentationCistern = new LevelSensor(Constants.LOW_LEVEL, Criteria.LOWER, new Sensor(alcoholicFermentationCistern, Constants.SECONDS, 0), 2);
    private final LevelSensor lowSensorMalolacticFermentationCistern = new LevelSensor(Constants.LOW_LEVEL, Criteria.LOWER, new Sensor(malolacticFermentationCistern, Constants.SECONDS, 0), 3);

    private final LevelSensor highSensorAlcoholicFermentationCistern = new LevelSensor(Constants.HIGH_LEVEL, Criteria.HIGHER, new Sensor(alcoholicFermentationCistern, Constants.SECONDS, 0), 4);
    private final LevelSensor highSensorMalolacticFermentationCistern = new LevelSensor(Constants.HIGH_LEVEL, Criteria.HIGHER, new Sensor(malolacticFermentationCistern, Constants.SECONDS, 0), 4);
    private final LevelSensor highSensorStorageCistern = new LevelSensor(Constants.HIGH_LEVEL_STORAGE, Criteria.HIGHER, new Sensor(storageCistern, Constants.SECONDS, 0), 4);

    // Stages
    private final Stages stages = new Stages();

    private Boolean systemRunning = Boolean.FALSE;

    private Boolean aFToMFValvePreviousState = Boolean.FALSE;

    private Boolean grapesToAFValvePreviousState = Boolean.FALSE;

    private Boolean mFToStorageValvePreviousState = Boolean.FALSE;

    private Valve tryingToOpen;

    public PLC(MainWindow window) {
        this.window = window;
        temperatureEnvironment.setName(Constants.TEMPERATURE_NAME);
        luminosityEnvironment.setName(Constants.LUMINOSITY_NAME);
        humidityEnvironment.setName(Constants.HUMIDITY_NAME);
        grapesContainer.addContent(Constants.GRAPES_CISTERN_START_AMOUNT);
        this.window.setAmountGrapesContainerValue(Constants.GRAPES_CISTERN_START_AMOUNT);
        this.window.setPLC(this);
        Helper.pause();
    }

    public void start() {
        synchronized (this) {
            Starter process = new Starter();
            process.start();
        }
    }

    private void alcoholicFermentationStage() {
        temperatureRegulator.reguleValue(Constants.ALCOHOLIC_FERMENTATION_AIM_TEMPERATURE);
        luminosityRegulator.reguleValue(Constants.FERMENTATION_AIM_LUMINOSITY);
        CompleteLogger.getLogger().info("Waiting to get the correct temperature and luminosity for Alcoholic Fermentation");
        while ((!temperatureSensor.getSensedValue().isInRangeOf(Constants.ALCOHOLIC_FERMENTATION_AIM_TEMPERATURE, Constants.ALCOHOLIC_FERMENTATION_WINDOW_TEMPERATURE / 3))
                || (!luminositySensor.getSensedValue().isInRangeOf(Constants.FERMENTATION_AIM_LUMINOSITY, Constants.FERMENTATION_WINDOW_LUMINOSITY / 2))) {
            CompleteLogger.getLogger().debug("Temperature " + temperatureSensor.getSensedValue().getValue());
            CompleteLogger.getLogger().debug("Luminosity " + luminositySensor.getSensedValue().getValue());
            Helper.getInstance().logicMiniPause();
        }

        SystemMonitor systemMonitorTemperature = new SystemMonitor();
        SystemMonitor systemMonitorLuminosity = new SystemMonitor();
        Boolean error = Boolean.TRUE;
        stages.resetAlcoholicFermentation();
        while (error) {
            Helper.getInstance().logicMiniPause();
            CompleteLogger.getLogger().info("Temperature " + temperatureSensor.getSensedValue().getValue());
            CompleteLogger.getLogger().info("Luminosity " + luminositySensor.getSensedValue().getValue());
            CompleteLogger.getLogger().message("Starting Alcoholic Fermentation");
            try {
                systemMonitorTemperature.controlValue(temperatureSensor, Constants.ALCOHOLIC_FERMENTATION_AIM_TEMPERATURE, Constants.ALCOHOLIC_FERMENTATION_WINDOW_TEMPERATURE);
                systemMonitorLuminosity.controlValue(luminositySensor, Constants.FERMENTATION_AIM_LUMINOSITY, Constants.FERMENTATION_WINDOW_LUMINOSITY);

                stages.doAlcoholicFermentation();
                while (!stages.isAlcoholicFermentationFinished()) {
                    systemMonitorTemperature.checkState();
                    systemMonitorLuminosity.checkState();
                    Helper.getInstance().logicMiniPause();
                }
                error = Boolean.FALSE;
            } catch (Exception e) {
                error(e.getMessage());
            }
        }
        systemMonitorTemperature.stop();
        systemMonitorLuminosity.stop();
        CompleteLogger.getLogger().message("Alcoholic Fermentation is finished");

    }

    private void alcoholicToMalolacticStage() {
        CompleteLogger.getLogger().message("Starting fill Malolactic Fermentation Cistern");
        try {
            openAFToMFValve();
            aFToMFValvePreviousState = Boolean.TRUE;
        } catch (Exception e) {
            error(e.getMessage());
        }
        while (!lowSensorAlcoholicFermentationCistern.isLevelReached() && !highSensorMalolacticFermentationCistern.isLevelReached()) {
            Helper.getInstance().logicMiniPause();
        }
        while (aFToMFValve.isOpen()) {
            try {
                Helper.getInstance().logicMiniPause();
                closeAFToMFValve();
            } catch (Exception e) {
                error(e.getMessage());
            }finally{
                aFToMFValvePreviousState = Boolean.FALSE;
                Helper.getInstance().logicMiniPause();
            }
        }
        CompleteLogger.getLogger().info("alcoholicFermentationCistern = " + alcoholicFermentationCistern.getValue().getValue());
        CompleteLogger.getLogger().info("malolacticFermentationCistern = " + malolacticFermentationCistern.getValue().getValue());
        CompleteLogger.getLogger().message("Malolactic Fermentation Cistern filled");

    }

    private void malolacticFermentationStage() {
        temperatureRegulator.reguleValue(Constants.MALOLACTIC_FERMENTATION_AIM_TEMPERATURE);
        CompleteLogger.getLogger().info("Waiting to get the correct temperature and luminosity for Malolactic Fermentation");
        while ((!temperatureSensor.getSensedValue().isInRangeOf(Constants.MALOLACTIC_FERMENTATION_AIM_TEMPERATURE, Constants.MALOLACTIC_FERMENTATION_WINDOW_TEMPERATURE / 3))
                || (!luminositySensor.getSensedValue().isInRangeOf(Constants.FERMENTATION_AIM_LUMINOSITY, Constants.FERMENTATION_WINDOW_LUMINOSITY / 2))) {
            CompleteLogger.getLogger().debug("Temperature " + temperatureSensor.getSensedValue().getValue());
            CompleteLogger.getLogger().debug("Luminosity " + luminositySensor.getSensedValue().getValue());

            Helper.getInstance().logicMiniPause();
        }

        SystemMonitor systemMonitorTemperature = new SystemMonitor();
        SystemMonitor systemMonitorLuminosity = new SystemMonitor();
        Boolean error = Boolean.TRUE;
        stages.resetMalolacticFermentation();
        while (error) {
            Helper.getInstance().logicMiniPause();
            CompleteLogger.getLogger().info("Temperature " + temperatureSensor.getSensedValue().getValue());
            CompleteLogger.getLogger().info("Luminosity " + luminositySensor.getSensedValue().getValue());
            CompleteLogger.getLogger().message("Starting Malolactic Fermentation");

            try {
                systemMonitorTemperature.controlValue(temperatureSensor, Constants.MALOLACTIC_FERMENTATION_AIM_TEMPERATURE, Constants.MALOLACTIC_FERMENTATION_WINDOW_TEMPERATURE);

                systemMonitorLuminosity.controlValue(luminositySensor, Constants.FERMENTATION_AIM_LUMINOSITY, Constants.FERMENTATION_WINDOW_LUMINOSITY);
                stages.doMalolacticFermentation();
                while (!stages.isMalolacticFermentationFinished()) {
                    systemMonitorTemperature.checkState();
                    systemMonitorLuminosity.checkState();
                    Helper.getInstance().logicMiniPause();
                }
                error = Boolean.FALSE;
            } catch (Exception e) {
                error(e.getMessage());
            }
        }
        systemMonitorTemperature.stop();
        systemMonitorLuminosity.stop();
        CompleteLogger.getLogger().message("Malolactic Fermentation is finished");
    }

    private void error(String e) {
        stop();
        CompleteLogger.getLogger().error(e);
    }

    private void malolacticToStorageStage() {
        CompleteLogger.getLogger().message("Starting fill Storage Cistern");
        try {
            openMFToStorageValve();
            mFToStorageValvePreviousState = Boolean.TRUE;
        } catch (Exception e) {
            error(e.getMessage());
        }
        while (!lowSensorMalolacticFermentationCistern.isLevelReached() && !highSensorStorageCistern.isLevelReached()) {
            Helper.getInstance().logicMiniPause();
        }
        while (mFToStorageValve.isOpen()) {
            try {
                Helper.getInstance().logicMiniPause();
                closeMFToStorageValve();
            } catch (Exception e) {
                error(e.getMessage());
            }finally{
                mFToStorageValvePreviousState = Boolean.FALSE;
                Helper.getInstance().logicMiniPause();
            }
        }
        CompleteLogger.getLogger().info("malolacticFermentationCistern = " + malolacticFermentationCistern.getValue().getValue());
        CompleteLogger.getLogger().info("storageCistern = " + storageCistern.getValue().getValue());
        CompleteLogger.getLogger().message("Storage Cistern filled");

    }

    private void poundingStage() {
        pounding.activate();
        CompleteLogger.getLogger().message("Starting fill Alcoholic Fermentation Cistern");
        try {
            openGrapesToAFValve();
            grapesToAFValvePreviousState = Boolean.TRUE;
        } catch (Exception e) {
            error(e.getMessage());
        }

        while (!lowSensorGrapesContainer.isLevelReached() && !highSensorAlcoholicFermentationCistern.isLevelReached()) {
            Helper.getInstance().logicMiniPause();
        }
        while (grapesToAFValve.isOpen()) {
            try {
                Helper.getInstance().logicMiniPause();
                closeGrapesToAFValve();
            } catch (Exception e) {
                error(e.getMessage());
            }finally{
                grapesToAFValvePreviousState = Boolean.FALSE;
                Helper.getInstance().logicMiniPause();
            }
        }
        if (lowSensorGrapesContainer.isLevelReached() && !highSensorAlcoholicFermentationCistern.isLevelReached()) {
            error("Please, load more grapes in the grape container");
        }
        CompleteLogger.getLogger().info("grapesContainer = " + grapesContainer.getValue().getValue());
        CompleteLogger.getLogger().info("alcoholicFermentationCistern = " + alcoholicFermentationCistern.getValue().getValue());

        pounding.deactivate();
        CompleteLogger.getLogger().message("Alcoholic Fermentation Cistern filled");
    }

    private void closeGrapesToAFValve() throws Exception {
        synchronized (this) {
            while (!grapesToAFValve.isFullyClose()) {
                grapesToAFValve.closeStep();
            }
        }
    }

    private void openGrapesToAFValve() throws Exception {
        synchronized (this) {
            while (!grapesToAFValve.isFullyOpen()) {
                grapesToAFValve.openStep();
            }
        }
    }

    private void closeAFToMFValve() throws Exception {
        synchronized (this) {
            while (!aFToMFValve.isFullyClose()) {
                aFToMFValve.closeStep();
            }
        }
    }

    private void openAFToMFValve() throws Exception {
        synchronized (this) {
            while (!aFToMFValve.isFullyOpen()) {
                aFToMFValve.openStep();
            }
        }
    }

    private void openValve(Valve valve) throws Exception {
        synchronized (this) {
            while (!valve.isFullyOpen()) {
                valve.openStep();
            }
        }
    }

    private void closeMFToStorageValve() throws Exception {
        synchronized (this) {
            while (!mFToStorageValve.isFullyClose()) {
                mFToStorageValve.closeStep();
            }
        }
    }

    private void openMFToStorageValve() throws Exception {
        synchronized (this) {
            while (!mFToStorageValve.isFullyOpen()) {
                mFToStorageValve.openStep();
            }
        }
    }

    private void storageStage() {
        temperatureRegulator.reguleValue(Constants.STORAGE_AIM_TEMPERATURE);
        luminosityRegulator.reguleValue(Constants.STORAGE_AIM_LUMINOSITY);
        humidityRegulator.reguleValue(Constants.STORAGE_AIM_HUMIDITY);
        CompleteLogger.getLogger().info("Waiting to get the correct temperature, luminosity and humidity for the storage");
        while ((!temperatureSensor.getSensedValue().isInRangeOf(Constants.STORAGE_AIM_TEMPERATURE, Constants.STORAGE_WINDOW_TEMPERATURE / 3))
                || (!luminositySensor.getSensedValue().isInRangeOf(Constants.STORAGE_AIM_LUMINOSITY, Constants.STORAGE_WINDOW_LUMINOSITY / 2))
                || (!humiditySensor.getSensedValue().isInRangeOf(Constants.STORAGE_AIM_HUMIDITY, Constants.STORAGE_WINDOW_HUMIDITY / 2))) {
            CompleteLogger.getLogger().debug("Temperature " + temperatureSensor.getSensedValue().getValue());
            CompleteLogger.getLogger().debug("Luminosity " + luminositySensor.getSensedValue().getValue());
            CompleteLogger.getLogger().debug("Humidity " + humiditySensor.getSensedValue().getValue());
            Helper.getInstance().logicMiniPause();
        }

        SystemMonitor systemMonitorTemperature = new SystemMonitor();

        SystemMonitor systemMonitorLuminosity = new SystemMonitor();

        SystemMonitor systemMonitorHumidity = new SystemMonitor();
        Boolean error = Boolean.TRUE;
        stages.resetStorage();
        while (error) {
            Helper.getInstance().logicMiniPause();
            CompleteLogger.getLogger().info("Temperature " + temperatureSensor.getSensedValue().getValue());
            CompleteLogger.getLogger().info("Luminosity " + luminositySensor.getSensedValue().getValue());
            CompleteLogger.getLogger().info("Humidity " + humiditySensor.getSensedValue().getValue());
            CompleteLogger.getLogger().message("Starting Storage");

            try {
                systemMonitorTemperature.controlValue(temperatureSensor, Constants.STORAGE_AIM_TEMPERATURE, Constants.STORAGE_WINDOW_TEMPERATURE);
                systemMonitorLuminosity.controlValue(luminositySensor, Constants.STORAGE_AIM_LUMINOSITY, Constants.STORAGE_WINDOW_LUMINOSITY);
                systemMonitorHumidity.controlValue(humiditySensor, Constants.STORAGE_AIM_HUMIDITY, Constants.STORAGE_WINDOW_HUMIDITY);

                stages.doStorage();
                while (!stages.isStorageFinished()) {
                    systemMonitorTemperature.checkState();
                    systemMonitorLuminosity.checkState();
                    systemMonitorHumidity.checkState();
                    Helper.getInstance().logicMiniPause();
                }
                error = Boolean.FALSE;
            } catch (Exception e) {
                error(e.getMessage());
            }
        }
        systemMonitorTemperature.stop();
        systemMonitorLuminosity.stop();
        systemMonitorHumidity.stop();
        temperatureRegulator.stop();
        luminosityRegulator.stop();
        humidityRegulator.stop();
        CompleteLogger.getLogger().message("Storage is finished, the wine is ready to bottle");
    }

    private class ViewUpdator extends Thread {

        @Override
        public void run() {
            while (true) {
                window.setAmountGrapesContainerValue(grapesContainer.getValue().getValue());
                window.setAmountAlcoholicCisternValue(alcoholicFermentationCistern.getValue().getValue());
                window.setAmountMalolacticCisternValue(malolacticFermentationCistern.getValue().getValue());
                window.setAmountStorageCisternValue(storageCistern.getValue().getValue());

                window.setValve1Value(grapesToAFValve.getOpening() * 100);
                window.setValve2Value(aFToMFValve.getOpening() * 100);
                window.setValve3Value(mFToStorageValve.getOpening() * 100);

                window.setAlcoholicFermentationBarValue(stages.getAlcoholicFermentationState());
                window.setMalolacticFermentationBarValue(stages.getMalolacticFermentationState());
                window.setStorageBarValue(stages.getStorageState());

                window.setPoundingState(pounding.isActivated());

                window.setLevelSensor1State(!lowSensorGrapesContainer.isLevelReached());
                window.setLevelSensor2State(highSensorAlcoholicFermentationCistern.isLevelReached());
                window.setLevelSensor3State(!lowSensorAlcoholicFermentationCistern.isLevelReached());
                window.setLevelSensor4State(highSensorMalolacticFermentationCistern.isLevelReached());
                window.setLevelSensor5State(!lowSensorMalolacticFermentationCistern.isLevelReached());
                window.setLevelSensor6State(highSensorStorageCistern.isLevelReached());
                window.setTemperature(temperatureSensor.getSensedValue().getValue());
                window.setLuminosity(luminositySensor.getSensedValue().getValue());
                window.setHumidity(humiditySensor.getSensedValue().getValue());
            }
        }

    }

    private class Starter extends Thread {

        @Override
        public void run() {
            if (!systemRunning) {
                CompleteLogger.getLogger().message("Restarting");
                systemRunning = Boolean.TRUE;
                ViewUpdator viewUpdator = new ViewUpdator();
                viewUpdator.start();
                if (Helper.getInstance().getPaused()) {
                    openAllValves();
                    Helper.getInstance().setPaused(Boolean.FALSE);
                } else {
                    poundingStage();
                    Helper.getInstance().logicPause();
                    alcoholicFermentationStage();
                    Helper.getInstance().logicPause();
                    alcoholicToMalolacticStage();
                    Helper.getInstance().logicPause();
                    malolacticFermentationStage();
                    Helper.getInstance().logicPause();
                    malolacticToStorageStage();
                    Helper.getInstance().logicPause();
                    storageStage();
                }
            }
        }
    }

    private class Stopper extends Thread {

        @Override
        public void run() {
            if (systemRunning) {
                closeAllValves();
                systemRunning = Boolean.FALSE;
                Helper.getInstance().setPaused(Boolean.TRUE);
            }
        }
    }

    public void stop() {
        synchronized (this) {
            Stopper stop = new Stopper();
            stop.start();
        }
    }

    private void closeAllValves() {
        synchronized (this) {
            try {
                aFToMFValvePreviousState = aFToMFValve.isOpen();
                closeAFToMFValve();
                grapesToAFValvePreviousState = grapesToAFValve.isOpen();
                closeGrapesToAFValve();
                mFToStorageValvePreviousState = mFToStorageValve.isOpen();
                closeMFToStorageValve();
            } catch (Exception e) {
                error(e.getMessage());
            }
        }
    }

    private void openAllValves() {
        synchronized (this) {
            try {
                if (tryingToOpen != null){
                    openValve(tryingToOpen);
                }
                if (aFToMFValvePreviousState) {
                    tryingToOpen = aFToMFValve;
                    openAFToMFValve();
                    aFToMFValvePreviousState = Boolean.FALSE;
                }
                if (grapesToAFValvePreviousState) {
                    tryingToOpen = grapesToAFValve;
                    openGrapesToAFValve();
                    grapesToAFValvePreviousState = Boolean.FALSE;
                }
                if (mFToStorageValvePreviousState) {
                    tryingToOpen = mFToStorageValve;
                    openMFToStorageValve();
                    mFToStorageValvePreviousState = Boolean.FALSE;
                }
                tryingToOpen = null;
            } catch (Exception e) {
                error(e.getMessage());
            }
        }
    }

    public void amortize() {
        temperatureRegulator.amortize();
        luminosityRegulator.amortize();
        humidityRegulator.amortize();
        grapesToAFValve.amortize();
        aFToMFValve.amortize();
        mFToStorageValve.amortize();

    }

    public void repair() {
        temperatureRegulator.repair();
        luminosityRegulator.repair();
        humidityRegulator.repair();
        grapesToAFValve.repair();
        aFToMFValve.repair();
        mFToStorageValve.repair();
    }

}
